﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace NinjaToolbox.PhysicsSystem
{
    public class PhysicsSystemManager : MonoBehaviour
    {
        static bool applicationIsQuitting = false;

        static PhysicsSystemManager mInstance;
        public static PhysicsSystemManager Instance
        {
            get
            {
                if (applicationIsQuitting) return null;

                if (mInstance == null)
                {
                    mInstance = new GameObject("[PhysicsSystemManager]").AddComponent<PhysicsSystemManager>();
                    UnityEngine.Object.DontDestroyOnLoad(mInstance.gameObject);
                }

                return mInstance;
            }
        }

        public struct TriggerInfo
        {
            public PhysicsObject PhysicsObject;
            public ITriggerObject TriggerObject;
            public IPhysicsShape CachedPhysicsShape;
            public bool RemoveMark;

            public List<TriggerInfo> TriggerEnterList;


            public static bool TriggerEnterListIsContains(List<TriggerInfo> list, TriggerInfo compareObject, out int index)
            {
                index = -1;

                for (int i = 0, iMax = list.Count; i < iMax; i++)
                {
                    if (list[i].PhysicsObject == compareObject.PhysicsObject)
                    {
                        index = i;
                        return true;
                    }
                }

                return false;
            }
        }

        public struct IntersectInfo
        {
            public int X;
            public int Y;
            public bool IsIntersect;
        }

        bool mIsUpdating;
        List<TriggerInfo> mTriggerInfoList;
        List<ColliderObject> mColliderObjectList;
        IntersectInfo[] mCacheIntersectPairArray;
        IPhysicsLayerMatrix mPhysicsLayerMatrix;

        public List<ColliderObject> ColliderObjectList { get { return mColliderObjectList; } }


        public void SetPhysicsLayerMatrix(IPhysicsLayerMatrix physicsLayerMatrix)
        {
            mPhysicsLayerMatrix = physicsLayerMatrix;
        }

        public void ResetPhysicsLayerMatrix()
        {
            mPhysicsLayerMatrix = new DefaultPhysicsLayerMatrix();
        }

        public void RegistColliderObject(ColliderObject colliderObject)
        {
            mColliderObjectList.Add(colliderObject);
        }

        public void UnregistColliderObject(ColliderObject colliderObject)
        {
            mColliderObjectList.Remove(colliderObject);
        }

        public void RegistTriggerObject(ITriggerObject triggerObject, PhysicsObject physicsObject)
        {
            var newTriggerInfo = new TriggerInfo() { TriggerObject = triggerObject, PhysicsObject = physicsObject, TriggerEnterList = new List<TriggerInfo>(mTriggerInfoList.Count) };

            if (physicsObject.otherSettings.isAcitivityObject)
                mTriggerInfoList.Insert(0, newTriggerInfo);
            else
                mTriggerInfoList.Add(newTriggerInfo);

            UpdateIntersectList();
        }

        public void UnregistTriggerObject(ITriggerObject triggerObject)
        {
            var index = mTriggerInfoList.FindIndex(m => m.TriggerObject == triggerObject);
            UnregistTriggerObject(index);
        }

        public bool IsIntersectWithSceneColliders(IPhysicsShape physicsShape, out IPhysicsShape firstIntersect)
        {
            return IsIntersectWithSceneColliders(physicsShape, out firstIntersect, ~0);
        }

        public bool IsIntersectWithSceneColliders(IPhysicsShape physicsShape, out IPhysicsShape firstIntersect, int layerMask)
        {
            firstIntersect = default(IPhysicsShape);

            for (int i = 0, iMax = mColliderObjectList.Count; i < iMax; i++)
            {
                var item = mColliderObjectList[i];

                var currentMask = 1 << item.Layer;
                if ((layerMask & currentMask) != currentMask) continue;

                if (item.PhysicsShape.IntersectsTest(physicsShape))
                {
                    firstIntersect = item.PhysicsShape;
                    return true;
                }
            }

            return false;
        }

        public void ForeachRegistedPhysicsObjects(Func<PhysicsObject, bool> action, int layerMask = ~0)
        {
            for (int i = 0, iMax = mTriggerInfoList.Count; i < iMax; i++)
            {
                var item = mTriggerInfoList[i];
                if ((layerMask & item.PhysicsObject.Layer) != item.PhysicsObject.Layer) continue;

                if (action != null)
                {
                    if (!action(item.PhysicsObject))
                        break;
                }
            }
        }

        public bool IsHitToOtherPhysicsObjects(PhysicsObject physicsObject, Action<PhysicsObject> onHit = null)
        {
            var result = false;

            for (int i = 0, iMax = mTriggerInfoList.Count; i < iMax; i++)
            {
                var item = mTriggerInfoList[i];
                if (physicsObject == item.PhysicsObject) continue;

                var isHit = physicsObject.PhysicsShape.IntersectsTest(item.PhysicsObject.PhysicsShape);

                if (isHit)
                {
                    if (onHit != null)
                        onHit(item.PhysicsObject);

                    result = true;
                }
            }

            return result;
        }

        public void ManualUpdate()
        {
            ExecuteUpdate();
        }

        void Awake()
        {
            mTriggerInfoList = new List<TriggerInfo>(30);
            mColliderObjectList = new List<ColliderObject>(30);
            mCacheIntersectPairArray = new IntersectInfo[0];

            mPhysicsLayerMatrix = new DefaultPhysicsLayerMatrix();
        }

        void OnDestroy()
        {
            applicationIsQuitting = true;
        }

        void LateUpdate()
        {
            ExecuteUpdate();
        }

        void ExecuteUpdate()
        {
            mIsUpdating = true;

            UnityEngine.Profiling.Profiler.BeginSample("s1");
            var max = mTriggerInfoList.Count;
            for (int i = 0; i < max; i++)
            {
                var triggerInfoX = mTriggerInfoList[i];
                triggerInfoX.CachedPhysicsShape = triggerInfoX.PhysicsObject.PhysicsShape;
                mTriggerInfoList[i] = triggerInfoX;
            }
            UnityEngine.Profiling.Profiler.EndSample();

            var intersectPairCount = 0;

            UnityEngine.Profiling.Profiler.BeginSample("s2");
            for (int x = 0; x < max; x++)
            {
                var triggerInfoX = mTriggerInfoList[x];
                if (!triggerInfoX.PhysicsObject.otherSettings.isAcitivityObject) break;

                for (int y = x + 1; y < max; y++)
                {
                    var triggerInfoY = mTriggerInfoList[y];
                    if (!mPhysicsLayerMatrix.IsIntersect(triggerInfoX.PhysicsObject.Layer, triggerInfoY.PhysicsObject.Layer)) continue;

                    var isIntersect = triggerInfoX.CachedPhysicsShape.IntersectsTest(triggerInfoY.CachedPhysicsShape);
                    mCacheIntersectPairArray[intersectPairCount] = new IntersectInfo() { X = x, Y = y, IsIntersect = isIntersect };
                    intersectPairCount++;
                }

                mTriggerInfoList[x] = triggerInfoX;
            }
            UnityEngine.Profiling.Profiler.EndSample();

            UnityEngine.Profiling.Profiler.BeginSample("s3");
            for (int i = 0; i < intersectPairCount; i++)
            {
                var item = mCacheIntersectPairArray[i];

                var triggerInfoX = mTriggerInfoList[item.X];
                var triggerInfoY = mTriggerInfoList[item.Y];

                ExecuteTriggerInfoExitLogic(triggerInfoX, triggerInfoY, item.IsIntersect);
                ExecuteTriggerInfoExitLogic(triggerInfoY, triggerInfoX, item.IsIntersect);
            }//Exit Logic.

            for (int i = 0; i < intersectPairCount; i++)
            {
                var item = mCacheIntersectPairArray[i];

                var triggerInfoX = mTriggerInfoList[item.X];
                var triggerInfoY = mTriggerInfoList[item.Y];

                ExecuteTriggerInfoEnterAndStayLogic(triggerInfoX, triggerInfoY, item.IsIntersect);
                ExecuteTriggerInfoEnterAndStayLogic(triggerInfoY, triggerInfoX, item.IsIntersect);
            }//Enter and Stay Logic.
            UnityEngine.Profiling.Profiler.EndSample();

            UnityEngine.Profiling.Profiler.BeginSample("s4");
            mIsUpdating = false;
            var isHasRemove = false;
            //处理移除
            for (int i = max - 1; i >= 0; i--)
            {
                if (mTriggerInfoList[i].RemoveMark)
                {
                    UnregistTriggerObject(mTriggerInfoList[i].TriggerObject);
                    isHasRemove = true;
                }
            }

            if (isHasRemove)
                UpdateIntersectList();
            UnityEngine.Profiling.Profiler.EndSample();
        }

        void UpdateIntersectList()
        {
            //其实是个等差数列
            //1,2,3,4,5 = 4+3+2+1 = 10.
            //1+2+3...+N 的公式是 (1+N)*N/2
            //(1+4)*(4/2) = 10.

            var n = mTriggerInfoList.Count - 1;
            var newCount = Mathf.RoundToInt((1 + n) * (n / 2f));

            if (newCount > mCacheIntersectPairArray.Length)
                mCacheIntersectPairArray = new IntersectInfo[newCount];
        }

        void UnregistTriggerObject(int triggerObjectIndex)
        {
            if (mIsUpdating)
            {
                var temp = mTriggerInfoList[triggerObjectIndex];
                temp.RemoveMark = true;
                mTriggerInfoList[triggerObjectIndex] = temp;
            }
            else
            {
                var item = mTriggerInfoList[triggerObjectIndex];

                for (int i = 0, iMax = item.TriggerEnterList.Count; i < iMax; i++)
                {
                    var triggerEnter = item.TriggerEnterList[i];

                    triggerEnter.TriggerObject.ExecuteOnTriggerExit(item.PhysicsObject);
                    item.TriggerObject.ExecuteOnTriggerExit(triggerEnter.PhysicsObject);

                    triggerEnter.TriggerEnterList.Remove(item);
                }
                item.TriggerEnterList.Clear();
                mTriggerInfoList.RemoveAt(triggerObjectIndex);

                UpdateIntersectList();
            }
        }

        void ExecuteTriggerInfoExitLogic(TriggerInfo x, TriggerInfo y, bool isIntersect)
        {
            if (x.PhysicsObject.otherSettings.optimizeMode) return;

            var index = -1;
            var isContainY = TriggerInfo.TriggerEnterListIsContains(x.TriggerEnterList, y, out index);

            if (!isIntersect && isContainY)//退出
            {
                x.TriggerEnterList.RemoveAt(index);
                x.TriggerObject.ExecuteOnTriggerExit(y.PhysicsObject);
            }
        }

        void ExecuteTriggerInfoEnterAndStayLogic(TriggerInfo x, TriggerInfo y, bool isIntersect)
        {
            if (x.PhysicsObject.otherSettings.optimizeMode)
            {
                if (isIntersect)
                    x.TriggerObject.ExecuteOnTriggerStay(y.PhysicsObject);

                return;
            }

            var index = -1;
            var isContainY = TriggerInfo.TriggerEnterListIsContains(x.TriggerEnterList, y, out index);

            if (!isIntersect && isContainY)//退出
            {
                x.TriggerEnterList.RemoveAt(index);
                x.TriggerObject.ExecuteOnTriggerExit(y.PhysicsObject);
            }

            if (isIntersect)
            {
                if (!isContainY)//进入
                {
                    x.TriggerEnterList.Add(y);
                    x.TriggerObject.ExecuteOnTriggerEnter(y.PhysicsObject);
                }
                else//逗留
                {
                    x.TriggerObject.ExecuteOnTriggerStay(y.PhysicsObject);
                }
            }
        }
    }
}
